package io.bdmc.core.snmp;

import io.bdmc.common.utils.CommonUtil;
import io.bdmc.common.utils.StringUtils;
import io.bdmc.modules.bss.entity.DeviceMonitorDynamic;
import io.bdmc.modules.bss.entity.DeviceStatusStatic;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.snmp4j.CommunityTarget;
import org.snmp4j.PDU;
import org.snmp4j.Snmp;
import org.snmp4j.event.ResponseEvent;
import org.snmp4j.event.ResponseListener;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.smi.*;
import org.snmp4j.transport.DefaultUdpTransportMapping;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/**
 * 演示： GET单个OID值
 * <p>
 * blog http://www.micmiu.com
 *
 * @author Michael
 */
@Component
public class SnmpData {

    private static final Logger logger = LoggerFactory.getLogger((SnmpData.class));

    public static final int DEFAULT_VERSION = SnmpConstants.version2c;
    public static final String DEFAULT_PROTOCOL = "udp";
    public static final int DEFAULT_PORT = 161;
    public static final long DEFAULT_TIMEOUT = 3 * 1000L;
    public static final int DEFAULT_RETRY = 3;

    /**
     * 创建对象communityTarget
     */
    public static CommunityTarget createDefault(String ip, String community) {
        Address address = GenericAddress.parse(DEFAULT_PROTOCOL + ":" + ip + "/" + DEFAULT_PORT);
        CommunityTarget target = new CommunityTarget();
        target.setCommunity(new OctetString(community));
        target.setAddress(address);
        target.setVersion(DEFAULT_VERSION);
        target.setTimeout(DEFAULT_TIMEOUT); // milliseconds
        target.setRetries(DEFAULT_RETRY);
        return target;
    }


    //关闭snmp
    private static void safeCloseSnmp(Snmp snmp) {
        if (snmp != null) {
            try {
                snmp.close();
            } catch (IOException ex1) {
                logger.error(ex1.toString());
            }
        }
    }

    //关闭transportMapping
    private static void safeCloneDefaultUdpTransportMapping(DefaultUdpTransportMapping transportMapping) {
        if (transportMapping != null) {
            try {
                transportMapping.close();
            } catch (IOException ex1) {
                logger.error(ex1.toString());
            }
        }
    }


    private static boolean checkWalkFinished(OID targetOID, PDU pdu, VariableBinding vb) {
        boolean finished = false;
        if (pdu.getErrorStatus() != 0) {
            finished = true;
        } else if (vb.getOid() == null) {
            finished = true;
        } else if (vb.getOid().size() < targetOID.size()) {
            finished = true;
        } else if (targetOID.leftMostCompare(targetOID.size(), vb.getOid()) != 0) {
            finished = true;
        } else if (Null.isExceptionSyntax(vb.getVariable().getSyntax())) {
            finished = true;
        } else if (vb.getOid().compareTo(targetOID) <= 0) {
            finished = true;
        }
        return finished;
    }

    /* 异步获取表格 */
    public static DeviceStatusStatic snmpAsyncWalkStatusStatic(String ip,String community) {
        //String community = "public";
        String oid = "1.3.6.1.4.1.48391.2.1";
        Map<String, Object> map = sendPduRequest(ip, oid, community);
        if(map==null||map.size()==0){
            return null;
        }
        DeviceStatusStatic deviceStatusStatic = new DeviceStatusStatic();
        CommonUtil.setField(deviceStatusStatic, map);
        deviceStatusStatic.setUpdateTime(LocalDateTime.now());
        return deviceStatusStatic;
    }

    //设置oid属性
    private static void setMapProperty(VariableBinding vb, Map<String, Object> map) {
        String oidStr = vb.getOid().toString();
        if (!oidStr.endsWith(".0") && !oidStr.endsWith(".49")) {
            oidStr = oidStr.substring(0, oidStr.length() - 2);
        }
        String property = SnmpDataCache.getPropertyByOid(oidStr);
        if (StringUtils.isNotEmpty(property)) {
            if (map.containsKey(property)) {
                String tmpValue = map.get(property).toString();
                tmpValue += ",";
                tmpValue += vb.getVariable().toString();
                map.put(property, tmpValue);
            } else {
                map.put(property, vb.getVariable().toString());
            }
        }
    }


    private static Map<String, Object> sendPduRequest(String ip, String oid, String community) {
        final CommunityTarget target = createDefault(ip, community);
        if (target.getAddress() == null) {
            return null;
        }
        Map<String, Object> map = new HashMap<>();
        Snmp snmp = null;
        DefaultUdpTransportMapping transport = null;
        try {
            transport = new DefaultUdpTransportMapping();
            transport.setSocketTimeout(500);
            snmp = new Snmp(transport);
            snmp.listen();
            final PDU pdu = new PDU();
            final OID targetOID = new OID(oid);
            final CountDownLatch latch = new CountDownLatch(1);
            pdu.add(new VariableBinding(targetOID));

            ResponseListener listener = new ResponseListener() {
                public void onResponse(ResponseEvent event) {
                    ((Snmp) event.getSource()).cancel(event.getRequest(), this);
                    try {
                        PDU response = event.getResponse();
                        if (response == null) {
                            logger.error("[ERROR]: response is null,ip:" + ip + ",oid:" + oid);
                            latch.countDown();
                        } else {
                            if (response.getErrorStatus() != 0) {
                                logger.error("[ERROR]: response status" + response.getErrorStatus() + " Text:"
                                        + response.getErrorStatusText() + ",ip:" + ip + ",oid:" + oid);
                                latch.countDown();
                            } else {
                                VariableBinding vb = response.get(0);
                                boolean finished = checkWalkFinished(targetOID, pdu, vb);
                                if (!finished) {
                                    setMapProperty(vb, map);
                                    pdu.setRequestID(new Integer32(0));
                                    pdu.set(0, vb);
                                    ((Snmp) event.getSource()).getNext(pdu, target, null, this);
                                } else {
                                    latch.countDown();
                                }
                            }
                        }
                    } catch (Exception e) {
                        logger.error(e.toString());
                        latch.countDown();
                    }
                }
            };
            snmp.getNext(pdu, target, null, listener);
            latch.await(10, TimeUnit.SECONDS);
            //logger.info("await result:" + await);
            return map;
        } catch (Exception ex) {
            logger.error(ex.toString());
            return null;
        } finally {
            safeCloseSnmp(snmp);
            safeCloneDefaultUdpTransportMapping(transport);
        }
    }

    /* 异步获取表格 */
    public static DeviceMonitorDynamic snmpAsyncWalkMonitorDynamic(String ip,String community) {
        String oid = "1.3.6.1.4.1.48391.3.5";
        DeviceMonitorDynamic deviceMonitorDynamic = new DeviceMonitorDynamic();
        Map<String, Object> map = sendPduRequest(ip, oid, community);
        if(map==null||map.size()==0){
            return null;
        }
        CommonUtil.setField(deviceMonitorDynamic, map);
        deviceMonitorDynamic.setDevIpAddr(ip);
        deviceMonitorDynamic.setUpdateTime(LocalDateTime.now());
        return deviceMonitorDynamic;
    }

    /* 设置信息 */
    public static synchronized boolean setPDU(String ip, String oid, Object valueObj, String community) {
        boolean setSuccess = false;
        Snmp snmp = null;
        CommunityTarget target;
        DefaultUdpTransportMapping transport = null;
        try {
            target = createDefault(ip, community);
            target.setTimeout(2000);
            PDU pdu = new PDU();
            if (valueObj instanceof String) {
                pdu.add(new VariableBinding(new OID(oid), new OctetString(valueObj.toString())));
            }
            if (valueObj instanceof Integer) {
                pdu.add(new VariableBinding(new OID(oid), new Integer32((Integer) valueObj)));
            }
            pdu.setType(PDU.SET);
            transport = new DefaultUdpTransportMapping();
            transport.setSocketTimeout(500);
            snmp = new Snmp(transport);
            snmp.listen();
            ResponseEvent response = snmp.send(pdu, target);
            PDU responsePdu = response.getResponse();
            if (responsePdu != null) {
                if (responsePdu.getErrorIndex() == PDU.noError && responsePdu.getErrorStatus() == PDU.noError) {
                    setSuccess = true;
                }
                logger.info("ip:" + ip + ",oid:" + oid + responsePdu);
            }
            Thread.sleep(100);
        } catch (Exception e) {
            logger.error("setPDU error:" + e);
        } finally {
            safeCloseSnmp(snmp);
            safeCloneDefaultUdpTransportMapping(transport);
        }
        return setSuccess;
    }

    /**
     * 设置设备的参数
     * @param ip          ip地址
     * @param channel     通道号，若无通道号，可填0
     * @param property    属性，动态和静态状态类里面的属性
     * @param valueObject 设置的值，可以是int或者string
     * @return boolean
     */
    public static boolean setSnmpValue(String ip, String channel, String property, Object valueObject, String community) {
        String oidStr = SnmpDataCache.getOidByProperty(property);
        if (StringUtils.isNotEmpty(oidStr)) {
            if (!oidStr.endsWith(".0") && !oidStr.endsWith(".49")) {
                oidStr += "." + channel;
            }
            return setPDU(ip, oidStr, valueObject,community);
        } else {
            return false;
        }

    }

    /**
     * 设置单次测量
     *
     * @param ip      ip
     * @param channel 端口
     * @param action  动作
     * @return boolean
     */
    public static boolean setSingleMeasure(String ip, Integer channel, int action,String community) {
        String oid;
        boolean setSuccess = false;
        oid = "1.3.6.1.4.1.48391.3.5.2.1.7." + channel;
        if (setPDU(ip, oid, action, community)) {
            setSuccess = true;
        }
        return setSuccess;
    }

    /**
     * 设置单次，自动，停止 测试模式
     * @param ip          ip地址
     * @param channel     通道号
     * @param action      1：启动自动测试，2：停止自动测试，3：启动单次测试
     * @param endPosition 测试的范围
     * @param refIndex    折射率
     * @param resolution  分辨率
     * @param pulseWidth  脉冲宽度
     * @param time        测量时间 秒
     * @return boolean
     */
    public static boolean setSingleArgs(String ip, Integer channel, int action, String endPosition, String refIndex, String resolution, String pulseWidth, int time,String community) {
        String oid;
        boolean setSuccess = false;
        if (action != 2) {
            oid = "1.3.6.1.4.1.48391.3.5.2.1.3." + channel;
            if (setPDU(ip, oid, endPosition,community)) {
                oid = "1.3.6.1.4.1.48391.3.5.2.1.4." + channel;
                if (setPDU(ip, oid, refIndex,community)) {
                    oid = "1.3.6.1.4.1.48391.3.5.2.1.5." + channel;
                    if (setPDU(ip, oid, resolution,community)) {
                        oid = "1.3.6.1.4.1.48391.3.5.2.1.8." + channel;
                        if (setPDU(ip, oid, pulseWidth,community)) {
                            oid = "1.3.6.1.4.1.48391.3.5.2.1.9." + channel;
                            if (setPDU(ip, oid, time,community)) {
                                setSuccess = true;
                            }
                        }
                    }
                }
            }
        } else {
            oid = "1.3.6.1.4.1.48391.3.5.2.1.7." + channel;
            if (setPDU(ip, oid, action,community)) {
                setSuccess = true;
            }
        }
        return setSuccess;
    }

    /**
     * 获取测试的进度
     *
     * @param ip ip
     * @return 返回sting 以逗号间隔 每个通道的状态
     */
    public static DeviceStatusProgress snmpAsyncWalkProgressStatus(String ip,String community) {
        List<String> oidList = new ArrayList<>();
        for (int i = 1; i < 9; i++) {
            //oidList.add("1.3.6.1.4.1.48391.3.5.2.1.6." + i);
            oidList.add("1.3.6.1.4.1.48391.3.5.2.1.10." + i);
        }
        CommunityTarget target = createDefault(ip, community);
        Snmp snmp = null;
        DefaultUdpTransportMapping transport = null;
        DeviceStatusProgress deviceStatusProgress = new DeviceStatusProgress();
        Map<String, Object> map = new HashMap<>();
        try {
            PDU pdu = new PDU();
            for (String oid : oidList) {
                pdu.add(new VariableBinding(new OID(oid)));
            }
            transport = new DefaultUdpTransportMapping();
            snmp = new Snmp(transport);
            snmp.listen();
            pdu.setType(PDU.GET);
            snmp.send(pdu, target);
            /* 异步获取 */
            final CountDownLatch latch = new CountDownLatch(1);
            ResponseListener listener = new ResponseListener() {
                public void onResponse(ResponseEvent event) {
                    ((Snmp) event.getSource()).cancel(event.getRequest(), this);
                    PDU response = event.getResponse();
                    if (response == null) {
                        logger.error("[ERROR]: response is null"+",ip:" + ip + ",获取测量进度");
                        latch.countDown();
                    } else {
                        if (response.getErrorStatus() != 0) {
                            logger.error("[ERROR]: response status" + response.getErrorStatus() + " Text:"
                                    + response.getErrorStatusText() + ",ip:" + ip + ",获取测量进度");
                            latch.countDown();
                        } else {
                            VariableBinding vb = response.get(0);
                            for (int i = 0; i < response.size(); i++) {
                                setMapProperty(vb, map);
                            }
                            latch.countDown();
                        }
                    }
                }
            };

            pdu.setType(PDU.GET);
            snmp.send(pdu, target, null, listener);
            latch.await(30, TimeUnit.SECONDS);
            //logger.info("await:" + await);
            CommonUtil.setField(deviceStatusProgress, map);
            deviceStatusProgress.setDevIpAddr(ip);
            return deviceStatusProgress;
        } catch (Exception e) {
            logger.error(e.toString());
            return null;
        } finally {
            safeCloseSnmp(snmp);
            safeCloneDefaultUdpTransportMapping(transport);
        }
    }
}